/* appjet:version 0.1 */
import('storage')
import('dlog')
import('cron')
import('lib-quickforms')
import('lib-moo')
import('lib-users')
import('lib-profile')
import('lib-datejs')
import('lib-pager')
import('lib-feed')
const googleKey = 'ABQIAAAAQWhMI9Xzn_9mHvRqhFZvpBRn7I8TEkwESvGAJ_w-cr5lll2YAhTubRsUV3Ynh7KD4kQH4m7JlYE1aA'


if(!storage.site) storage.site = {users:0,feeds:0,terms:0,interests:0,count:0}
if(!storage.feeds) storage.feeds = new StorableCollection()
if(!storage.entries) storage.entries = new StorableCollection()
if(!storage.terms) storage.terms = new StorableCollection()
if(!storage.termsIndex) storage.termsIndex = new StorableObject()
if(!storage.interests) storage.interests = new StorableCollection()
if(!storage.interestsIndex) storage.interestsIndex = new StorableObject()
if(!storage.relations) storage.relations = new StorableCollection()

function get_purge(){
    if(cool()||appjet.isPreview){
        //storage.users.remove({})
        //storage.feeds.remove({})
        storage.site = {users:0,feeds:0,terms:0,interests:0,count:0}
        storage.entries = new StorableCollection()
        storage.terms = new StorableCollection()
        storage.termsIndex = new StorableObject()
        storage.interests = new StorableCollection()
        storage.interestsIndex = new StorableObject()
        storage.relations = new StorableCollection()
        
        storage.users.forEach(function(u){
            u.count = 0
            u.interests = 0 
        })
    }
}

/* Utilities
******************************************************************************/
const perPage = 20

const yahooKey = 'uS4kfKXV34H_rBLPJa9QMa6WPLJ6RTA2Yj6NdH.5StejduIQHH5VmGitw48NECWJUUFlzo1c'
const yahooUrl = 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction'

function cool(){
    if(user.email == 'jesse@codery.com') return true
    else return false
}
function stripDomain(url) {
    if (url == null || url.length == 0) return ""
    url = url.toLowerCase().replace(/http:\/\//, '')
    var i = url.indexOf("/")
    if (i > -1) url = url.substring(0, i)
    var parts = url.split('.')
    var len = parts.length
    if (len < 3) return url
    var lastPart = parts[len-1]
    var secondPart
    secondPart = parts[len-2]
    var two = 2
    if (lastPart == "uk" && secondPart == "co") ++two
    if (len >= 0) return parts.splice(len-two, two).join('.')
    return ""
}
function parseDate(date){
    //Tue, 12 Aug 2008 20:36:35 -0700
    parseable = date.replace(/,/g,"").substring(4,25)
    d = Date.parse(parseable)
    if(d) return d
    else {
        dlog.error("couldn't parse date (",parseable ,") from (",date,")")
        return new Date()
    }
}
function niceDate(date) {
    if(typeof date == 'object') {
        if(Date.today().compareTo(date) == -1) return 'today'
        else if(Date.today().add({days:-1}).compareTo(date) == -1) return 'yesterday'
        else return date.toString('MM/dd/yy')
    } else return '02/24/82'
}
function niceTime(date) {
    if(typeof date == 'object') {
        if(date > (1).minutes().ago()) 
            return new Date(new Date() - date).toString('s')+" seconds ago"
        else if(date > (1).hours().ago()) 
            return  new Date(new Date() - date).toString('m')+" minutes ago"
        else if(date > (1).days().ago())
            return date.toString('h:mm')+' today'
        else if(Date.today().add({days:-1}).compareTo(date) == -1) 
            return date.toString('h:mm')+' yesterday'
        else 
            return date.toString('MM/dd/yy h:mm')
    } else return '02/24/82'
}
function niceFeed(feed){
    var feedLink = DIV({className:"feed"},
        IMG({ src:'http://www.google.com/s2/favicons?domain='+stripDomain(feed.link),
            alt:"feed icon",
            className:"feed_icon",
            border:"none",
            align:"top",
            margin:"2"
        }),
        A({ href:feed.link,
            title:feed.title
            },
            feed.title.substring(0, 20),
            raw((feed.title.length>20)?'&hellip;':'')
        )
    )
    if(cool()) feedLink.push(SPAN({style:'margin:5px'},link('/delete_feed?id='+feed.id, 'X')))
    return feedLink
}
function niceUser(user){
    return DIV({className:'user'},
        IMG({
            src:'http://www.gravatar.com/avatar/'+md5(user.email)+'?d=monsterid&s=18', 
            className:'avatar'
        }),
        A({href:'/profile/'+escape(user.name)},user.name)
    )
}
function niceEntry(entry, color){
    if(!color) color = randomColor()
    if(!entry.user) entry.user = {}
    if(!entry.user.backgroundColor) 
        entry.user.backgroundColor = '#'+color
    details = DL()
    if(entry.published)
        details.push(DT('at:'), DD(niceTime(entry.published)))
    if(entry.user)
        details.push(DT('by:'), DD(niceUser(entry.user)))
    if(entry.feed)
        details.push(DT('in:'), DD(niceFeed(entry.feed)))
    
    return TABLE({className:'entry', style:'background-color:'+entry.user.backgroundColor},
        TR(TD(H4({className:'title'},
            A({href:entry.link, target:'_blank'}, entry.title)
        ))),
        TR(TD(BLOCKQUOTE({className:'content'},
            raw(entry.content)
        ))),
        TR(TD({className:'details'}, details))
    )
}
function randomColor(){
    function randomHex(){
        a = new Array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f')
        return a[Math.floor(Math.random()*a.length)]
    }
    return randomHex()+randomHex()+randomHex()+randomHex()+randomHex()+randomHex()
}
function stale(a,b){
    return  a.updated - b.updated    
}
function lastAnalyzed(a,b){
    if(!a.analyzed) a.analyzed = new Date().addHours(-24)
    if(!b.analyzed) b.analyzed = new Date().addHours(-24)
    return a.analyzed - b.analyzed    
}
function pleaseLogin(){
    if(!user) {
        back = request.path
        if(request.query) back += '?'+request.query
        response.redirect('/login?back='+escape(back))
    } else return true
}

var feedForm = new QuickForm({action:'feed', method:'get'})
feedForm.addHeading('Add a feed')
feedForm.addInputText('url')
feedForm.addSubmit('add', "Add")

var searchForm = new QuickForm({action:'feed_search', method:'post'})
searchForm.addHeading('Search for your feeds')
searchForm.addInputText('query')
searchForm.addSubmit('search', "Search")

/* Basic Pages 
******************************************************************************/
function get_main(){
    if(user) response.redirect('entries')
    content.push(DIV({className:'message'},
        P("Almost every site generates a feed of the content you post. There's one for your Flickr photos, your Facebook posts, your blog, your Del.icio.us links. But who's really subscribing to all of them? Your mom? Wouldn't it be great to do something useful with all that stuff you're twittering, and bookmarking?"),
        P("Agglodex is a place for you to store all the feeds you make. All of em! Agglodex will have a look at all the cool stuff you're creating and sharing and figure out what you're interested in. Maybe we can even show you some stuff you haven't seen yet. Now your friends won't have to scour the Internet to find out what you've been up to."),
        P("To get started just ",link('register', 'register'))
    ))
}
function get_blog(){
    var feed = loadFeed('http://theneedforfeed.tumblr.com/rss')
    pageTitle = feed.title
    for(i=0; i<feed.entries.length; i++){
        content.push(DIV({className:'message', id:'entry'},
            H3(feed.entries[i].title),
            DIV(raw(feed.entries[i].content))
        ))
    }
}
/* Feed Pages 
******************************************************************************/
function get_feeds(){
    pageTitle = "All Feeds"
    var table = TABLE({className:'message'}, TR(
        TH('feed'),
        TH('user'),
        TH('added'),
        TH('updated')
    ))
    var feeds = storage.feeds.sort(stale).reverse().skip(request.params.page*perPage-perPage).limit(perPage)
    var n=0
    feeds.forEach(function(feed){
        table.push(TR({className:(n%2)?'even':'odd'},
            TD(niceFeed(feed)),
            TD(link('/profile/'+escape(feed.user.name),feed.user.name)),
            TD(niceTime(feed.added)),
            TD(niceTime(feed.updated))
        ))
        n++
    })
    content.push(table)
    content.push(pager('/feeds?', request.params.page, perPage, storage.feeds.size()))
}
function get_my_feeds(){
    pleaseLogin()
    pageTitle = 'My Feeds'
    message = TABLE(TR(
        TD(feedForm),
        TD(searchForm),
        TD(
            H3("Don't forget the bookmarklet!"),
            P("Drag this bookmarklet ",
            A({
                href:"javascript:location.href='http://agglodex.appjet.net/feed?url='+encodeURIComponent(location.href)",
                className:'bookmarklet'
            },"Agglogate"),
            " to your toolbar so you can easily add a feed from any of your profiles.")
    )))
    var feeds = storage.feeds.filter({user:user})
    user.feeds = feeds.size()
    if(!user.feeds) content.push(P("Enter the url of one of your feeds to get started"))
    else {
        var table = TABLE({className:'message'}, TR(
            TH('title'),
            TH('delete')
        ))
        n=0
        feeds.forEach(function(feed){
            table.push(TR({className:(n%2)?'even':'odd'},
                TD(niceFeed(feed)),
                TD(link('/delete_feed?id='+feed.id,"delete"))
            ))
            n++
        })
        content.push(table)
    }
}
function post_feed_search(){
    var urls = ''
    var i = 0
    findFeeds(request.params.query).forEach(function(f){
        i++
        urls += '&url='+escape(f.url)
    })
    response.redirect('feed?'+urls)
}
function get_feed() {
    pleaseLogin()
    pageTitle = 'Add a Feed'
    if(typeof request.params.url != 'object')
        request.params.url = Array(request.params.url)
    if(!request.params.url) response.redirect('home')
    else {
        //print(request.params.url.length)
        var list = UL()
        for(i=0;i<request.params.url.length;i++){
            //print(DIV(request.params.url[i]))
            var feed = loadFeed(request.params.url[i], 4)
            if(!feed) feed = loadFeed(lookupFeed(request.params.url[i]), 4)
            if(!feed) list.push(LI({className:'error'},
                "There's something wrong with ",
                link(request.params.url[i], "this feed.")
            ))
            else {
                var item = LI()
                var someFeed = storage.feeds.filter({url:feed.url}).first()
                if(!someFeed) { // feed doesn't exist in agglodex yet
                    var save = new QuickButton(
                        'Save This Feed',
                        {action:'feed', method:'post'},
                        {url:feed.url, 
                        link:feed.link,
                        title:feed.title,
                        description:feed.description}
                    )
                    item.push(TABLE({className:'message'}, TR(
                        TD(niceFeed(feed)," Only save this feed if these items were made by you."), 
                        TD(save)
                    )))
                } else { // feed is already in the system
                    var entries = storage.entries.filter({feed:someFeed})
                    item.push(DIV({className:'message'}, 
                        niceFeed(feed),
                        " belongs to ", 
                        niceUser(someFeed.user), 
                        " if you think this is wrong ",
                        link('mailto:jesse@codery.com', "contact me"))
                    )
                }
                // display feed
                if(feed){
                    var entries = DIV()
                    var color = randomColor()
                    for(var j=0; j<feed.entries.length; j++){
                        entries.push(niceEntry(feed.entries[j], color))
                    }
                    item.push(entries)
                }
                list.push(item)
            }
        } // endfor
        content.push(list)
    }
}
function post_feed() {
    pleaseLogin()
    if(!request.params.url) response.redirect('/my_feeds')
    else {
        var someFeed = storage.feeds.filter({url:request.params.url}).first()
        if(someFeed) error = "That feed has already been added"
        else {
            data = request.params
            data.user = user
            data.added = new Date()
            data.updated = (30).minutes().ago()
            storage.feeds.add(data)
            if(!user.feeds) user.feeds = 1
            else user.feeds++
            response.redirect('/my_feeds')
        }
    }
}
function get_delete_feed(){
    if(user && request.params.id) {
        var feed = getStorable(request.params.id)
        if(feed.user == user || cool()){
            storage.entries.filter({feed:feed}).forEach(function(entry){
                storage.entries.remove(entry)
            })
            storage.feeds.remove(feed)
        }
    }
    response.redirect('my_feeds')
}
/* Entry Pages 
******************************************************************************/
function get_entries(){
    pageTitle = "All Entries"
    var entries = storage.entries.sortBy('published').reverse().skip(request.params.page*perPage-perPage).limit(perPage)
    entriesDiv = DIV({id:'entries'})
    entries.forEach(function(entry){
        entriesDiv.push(niceEntry(entry))
    })
    content.push(entriesDiv)
    content.push(pager('/entries?', request.params.page, perPage, storage.entries.size()))
    Profile("displayed entries")
}

function get_my_entries(){
    pleaseLogin()
    if(!user.backgroundColor) user.backgroundColor = '#333333'
    message = FORM({action:'/entry_style', method:'post'},
        H3("Your Style"),
        SCRIPT(raw("""
        window.addEvent('domready', function() {
            var r = new MooRainbow('backgroundColorButton', {
                startColor: [51,51,51],
                wheel:true,
                onChange: function(color) {
                    $('backgroundColor').value = color.hex;
                    $('backgroundColorButton').style.backgroundColor = color.hex;
                }
            }); 
        });
        """)),
        DIV({id:'backgroundColorButton', style:'background: '+user.backgroundColor+'; padding: 5px'}, 'Background Color: ', INPUT({id:'backgroundColor', name:'backgroundColor'}))
        ,INPUT({type:'submit', value:'Save'})
    )
    pageTitle = "My Entries"
    
    var entries = storage.entries.filter({user:user}).sort(function(a,b){
        return b.published - a.published
    })
    user.entries = entries.size()
    var div = DIV({id:'entries'})
    entries.forEach(function(entry){
        div.push(niceEntry(entry))
    })
    content.push(div)
    Profile("displayed entries")
}
function post_entry_style(){
    user.backgroundColor = request.params.backgroundColor
    response.redirect('/my_entries')
}
/* Term Pages 
******************************************************************************/
function get_terms(){
    pageTitle = "All Terms"
    var table = TABLE(TR(
        TH('string'), 
        TH('count'),
        TH('interests'),
        TH('frequency')
    ))
    n=0
    //storage.terms.filter({relevant:true}).sort(lastAnalyzed).reverse().skip(
    storage.terms.sortBy('interests').reverse().skip(
        request.params.page*perPage-perPage
    ).limit(perPage).forEach(function(term){
        table.push(TR({className:(n%2)?'even':'odd'},
        TD(term.string),
        TD(term.count),
        TD(term.interests),
        TD(term.frequency)
        ))
        n++
    })
    content.push(
        table,
        pager('/terms?', request.params.page, perPage, storage.terms.filter({relevant:true}).size())
    )
    Profile("displayed terms")
}
function get_my_interests(){
    pleaseLogin()
    pageTitle = "My Interests"
    var table = TABLE(TR(
        TH('string'), 
        TH('score'),
        TH('my count'),
        TH('all count')
    ))
    n=0
    storage.interests.filter({user:user,relevant:true}).sortBy('score').skip(
        request.params.page*perPage-perPage
    ).limit(perPage).forEach(function(i){
        table.push(TR({className:(n%2)?'even':'odd'},
            TD(i.term.string),
            TD(i.score),
            TD(i.count),
            TD(i.term.count)
        ))
        n++
    })
    content.push(table, pager('/my_interests?', request.params.page, perPage, storage.interests.filter({user:user,relevant:true}).size()))
}
function get_interests(){
    content.push(H2("All Interests"))
    var table = TABLE({className:"message"}, TR(
        TH('string'),
        TH('user'),
        TH('count'),
        TH('score')
    ))
    n=0
    storage.interests.filter({relevant:true}).sortBy('score').reverse().skip(
        request.params.page*perPage-perPage
    ).limit(perPage).forEach(function(i){
        table.push(TR({className:(n%2)?'even':'odd'},
            TD(i.term.string),
            TD(niceUser(i.user)),
            TD(i.count),
            TD(i.score)
        ))
        n++
    })
    content.push(table, pager('/interests?', request.params.page, perPage, storage.interests.filter({relevant:true}).size()))
}

/* User Pages 
******************************************************************************/
function get_users(){
    pageTitle = "All Users"
    var table = TABLE({className:'message'},TR(
        TH('name'),
        TH('feeds'),
        TH('entries'),
        TH('joined'),
        TH('active')
    ))
    n=0
    storage.users.sort(function(a,b){
        if(!b.active) return true
        else if(!a.active) return false
        else return b.active- a.active
    }).forEach(function(u){
        table.push(TR({className:(n%2)?'even':'odd'},
            TD(niceUser(u)),
            TD(storage.feeds.filter({user:u}).size()),
            TD(storage.entries.filter({user:u}).size()),
            TD(niceDate(u.date)),
            TD(niceTime(u.active))
        ))
        n++
    })
    content.push(table)
    if(cool()){
        content.push(
            H3('Blast Users'),
            FORM({action:'/blast', method:'post'},
                LABEL('subject', INPUT({name:'subject'})),
                LABEL('body', TEXTAREA({name:'body'})),
                INPUT({type:'submit', value:'Blast'})
            )   
        )    
    }
}
function get_profile(){
    var name = request.path.split('/')[2]
    if(!name) response.redirect('home')
    var someUser = storage.users.filter({name:unescape(name)}).next()
    
    pageTitle = someUser.name.capitalize()+"'s Profile"
    
    var strings = Array()
    storage.interests.filter({user:someUser}).sortBy('score').
        reverse().limit(100).forEach(function(i){
        strings.push(i.term.string)
    })
    strings.sort()
    var interestList = UL({id:'interests', className:'folder'})
    for(i=0;i<strings.length;i++){
        if(interest = storage.interestsIndex[someUser.id][strings[i]])
            interestList.push(LI({style:'font-size:'+Math.round(interest.score)*3+'px'}, strings[i]))    
    }
    if(!interestList.length) interestList = H3("no interests")
    
    var feedList = UL({id:'feeds', className:'folder'})
    storage.feeds.filter({user:someUser}).sortBy('entries').reverse().forEach(function(feed){
        feedList.push(LI(niceFeed(feed)))
    })
    if(!feedList.length) feedList = H3("No Feeds")
    
    var similarList = UL({id:'similar', className:'folder'})
    storage.relations.filter({subject:someUser}).sortBy('score').forEach(function(r){
        similarList.push(LI(niceUser(r.object),'(',r.score,')'))
    })
    if(!similarList.length) H3("No Similar Users")
    
    var entryList = DIV({id:'entries', className:'folder'})
    storage.entries.filter({user:someUser}).sortBy('published').reverse()
        .skip(perPage*request.params.page-perPage).limit(perPage).forEach(function(entry){
        entryList.push(niceEntry(entry))
    })
    if(entryList.length) {
        entryList.push(pager(
            '/profile/'+someUser.name+'?', 
            request.params.page, 
            perPage, 
            storage.entries.filter({user:someUser}).size()
        ))
    } else entryList = H3("No Entries")
    
    /* put it together
    content.push(TABLE(TR(
        TD( 
            interestList,
            entryList,
            pager
        ),TD({valign:'top'}, 
            feedList, 
            similarList
        )
    )))*/
    
    content.push(DIV({className:'accordion'},
        H3({className:'toggler'}, "Interest"),
        interestList,
        H3({className:'toggler'}, "Feeds"),
        feedList,
        H3({className:'toggler'}, "Similar Users"),
        similarList,
        H3({className:'toggler'}, "Entries"),
        entryList
    ))
}
function get_my_similar_users(){
    pleaseLogin()
    pageTitle = "My Similar Users"
    table = TABLE(TR(
        TH('user'),
        TH('score')
    ))
    n=0
    storage.relations.filter({subject:user}).sortBy('score').forEach(function(r){
        table.push(TR({className:(n%2)?'even':'odd'},
            TD(niceUser(r.object)),
            TD(r.score)
        ))
        n++
    })
    content.push(table)
}
function get_fix(){
    storage.users.forEach(function(u){
        u.relevant = true
    })
}

/* Crons
******************************************************************************/
function get_cron(){
    if(request.params.unschedule)
        unscheduleAll()
    if(request.params.start)
        scheduleRepeating((1).minute().fromNow(), 1, '/'+request.params.start, {}, true, true)
    content.push(UL(
        LI(link('/cron?unschedule=true', 'unschedule')),
        LI(link('/cron?start=refresh', 'start refresh')),
        LI(link('/cron?start=analyze_entries', 'start analyzing entries')),
        LI(link('/cron?start=analyze_interests', 'start analyzing interests')),
        LI(link('/cron?start=analyze_users', 'start analyzing users')),
        LI(link('/cron?start=analyze_terms', 'start analyzing terms'))
    ))
    table = TABLE()
    listAll().forEach(function(c){
        table.push(TR(
            TD(c.path)
        ))
    })
    content.push(table)
}
function get_refresh(){cron_refresh()}
function cron_refresh(){
    storage.feeds.sort(stale).limit(3).forEach(function(feed){
        if(!feed.entries) feed.entries = storage.entries.filter({feed:feed}).size()
        var loadedFeed = loadFeed(feed.url)
        // here would be a good place to update the rest of the feed's fields in case they've changed
        var entries = loadedFeed.entries
        var n=0
        for(var i=0; i<entries.length; i++){
            var someEntry = storage.entries.filter({feed:feed, title:entries[i].title}).first()
            if(!someEntry) {
                var published = parseDate(entries[i].publishedDate)
                if(published > new Date()) published = new Date()
                var data = {
                    feed:feed,
                    user:feed.user,
                    added: new Date(),
                    analyzed:false,
                    title:entries[i].title,
                    link:entries[i].link,
                    author:entries[i].author,
                    published:published,
                    content:entries[i].content,
                    summary:entries[i].contentSnippet
                }
                var entry = storage.entries.add(data)
                feed.entries++
                if(!storage.site.entries) 
                    storage.site.entries = storage.entries.size()
                else
                    storage.site.entries++
                n++
            }
        }
        feed.updated = new Date()
        if(n) dlog.info("added "+n+" entries, "+feed.title+" now has "+feed.entries)
        //else dlog.info("no new entries found for "+feed.title)
    })
}
function get_analyze_entries(){cron_analyze_entries()}
function cron_analyze_entries(){ // This is were terms and interests are made
    const limit = 2000
    var processed = 0
    storage.entries.filter({analyzed:false}).sortBy('added').forEach(function(e){
        //print(DIV(e.title))
        text = (e.title+' '+e.content).replace(/<[a-zA-Z\/][^>]*>/g, ' ').toLowerCase().clean().substring(0,limit)
        words = text.match(/\b\w{3,}\b/g)
        if(words){
            // see if it's time to quit
            if((processed += words.length) > limit) 
                return false
            words.forEach(function(w){
                e.user.count++
                
                // term
                term = storage.termsIndex[w]
                if(!term) { // term doesn't exist, add it
                    term = storage.terms.add({
                        string:w, 
                        count:1,
                        interests:0,
                        frequency:0,
                        relevant:true
                    })
                    storage.site.terms++
                    storage.termsIndex[w] = term
                } else term.count++
                storage.site.count++
                
                // interest
                if(userIndex = storage.interestsIndex[e.user.id]){ // there's an interestIndex property for this user, load the interest from it
                    interest = userIndex[w]
                } else { // there's no interestIndex entry for this user, create it
                    userIndex = new StorableObject()
                    storage.interestsIndex[e.user.id] = userIndex
                    interest = false
                }
                if(!interest){ // interest doesn't exist, add it
                    interest = storage.interests.add({
                        user:e.user, 
                        term:term, 
                        count:1,
                        frequency:0,
                        score:0,
                        relevant:true,
                        analyzed: new Date().addHours(-24)
                    })
                    storage.site.interests++
                    interest.user.interests++
                    interest.term.interests++
                    userIndex[w] = interest
                } else interest.count++
            })
        }
        e.analyzed = new Date()
    })
    //if(appjet.isPreview) get_interests()
}
function get_analyze_interests(){cron_analyze_interests()}
function cron_analyze_interests(){
    storage.interests.sortBy('analyzed').limit(1000).forEach(function(i){
        // i.count : the number of times a user's feeds have contained a term
        // i.user.count : the total number of terms that a user's feeds have contained
        // i.frequency : term frequency
        i.frequency = i.count / i.user.count
        
        // i.term.interests : the number of users who have used a term
        // storage.users.size() : the total number of users on the site
        // i.term.frequency : frequency of the word for all users 
        
        //i.term.frequency = i.term.interests / storage.users.size()
        //i.term.frequency = Math.log(storage.users.size() / i.term.interests)
        i.term.frequency = i.term.count / storage.site.entries
        
        // there are different methods for computing the tf-idf, I'll try a few
        i.score1 = i.frequency / (i.term.count / storage.site.entries)
        i.score2 = i.frequency * Math.log(storage.users.size() / i.term.interests)
        i.score3 = Math.log(i.count + 1) * Math.log(storage.users.size() / i.term.interests)
        
        // i.score : the best tf-idf score I've come up with
        i.score = i.score3
        
        // determine if the interest is relevant or not
        i.relevant = (i.score > 4)? true : false
        
        i.analyzed = new Date()
        
        if(appjet.isPreview) {
            print(DIV(
                " user:",B(i.user.name),
                " term:",B(i.term.string),
                " i.count:",B(i.count),
                " t.count:",B(i.term.count),
                " t.interests:",B(i.term.interests),
                " score:",B(i.score)
            ))
        }
    })            
}

function get_analyze_users(){cron_analyze_users()}
function cron_analyze_users(){
    var subject = storage.users.sortBy('analyzed').first()
    print(subject.name)
    if(!subject.interests) 
        subject.relevant = false
    else {
        storage.users.filter({relevant:true}).sortBy('active').forEach(function(object){
            if(object.email != subject.email){
            
                relation = storage.relations.filter({subject:subject, object:object}).first()
                if(!relation) relation = storage.relations.add({subject:subject, object:object})
                noitaler = storage.relations.filter({subject:object, object:subject}).first()
                if(!noitaler) noitaler = storage.relations.add({subject:object, object:subject})
            
                relation.score = euclidean(subject, object)
                noitaler.score = relation.score
            
                print(DIV('score for '+subject.name+' and '+object.name+' is '+relation.score))
            
                relation.analyzed = new Date()
                noitaler.analyzed = new Date()
            }
        })
    }
    subject.analyzed = new Date()
    response.redirect('analyze_users')
}
function euclidean(subject, object){
    var sum = 0
    storage.interests.filter({user:object,relevant:true}).forEach(function(i){
        if(userIndex = storage.interestsIndex[subject.id]){
            if(interest = userIndex[i.term.string]) {
                if(interest.relevant) {
                    difference = interest.score - i.score
                    square = Math.pow(difference, 2)
                    if(typeof square != 'number') square = 0
                    sum = sum + square
                }
            }
        }
    })
    if(typeof sum != 'number') return 0
    else return 1/(1+Math.sqrt(sum))
}
function pearson(subject, object, interests){
    var shared = 0
    var subjectSum = 0
    var objectSum = 0
    var subjectSqSum = 0
    var objectSqSum = 0
    var productSum = 0
    storage.interests.filter({user:object,relevant:true}).forEach(function(i){
        if(interests[i.term.string]){
            shared++
            subjectSum += interests[i.term.string]
            objectSum += i.score
            subjectSqSum += Math.pow(interests[i.term.string],2)
            objectSqSum += Math.pow(i.score,2)
            productSum += interests[i.term.string] * i.score
        }
    })
    if(shared) {
        num = productSum - (subjectSum * objectSum / shared)
        den = Math.sqrt(
            (subjectSqSum-Math.pow(subjectSum,2) / shared) * 
            (objectSqSum -Math.pow(objectSum ,2) / shared)
        )
        if(den) return Math.round((num / den) * 100)/100
    }
    return false
}
function tanimoto(subject, object, interests){
    var shared = 0
    var subjectSum = 0
    var objectSum = 0
    var termSum = 0
    storage.interests.filter({user:object,relevant:true}).forEach(function(i){
        if(interests[i.term.string]){
            shared++
            subjectSum += interests[i.term.string]
            objectSum += i.difference
            termSum += i.term.difference
        }
    })
    if(shared) {
        num = subjectSum * objectSum / shared
        den = (Math.pow(termSum,2) / shared) * 2 - (subjectSum * objectSum) / shared
        if(den) return num/den
    }
    return false
}

function debug(something){
    if(appjet.isPreview) content.push(something)
}
/* Display Logic
******************************************************************************/
page.head.write(LINK({rel:'stylesheet', type:'text/css', href:'http://attnfeed.com/static/agglodex.css'}))
page.head.write(LINK({rel:'stylesheet', type:'text/css', href:'http://attnfeed.com/static/mooRainbow.css'}))
page.head.write(LINK({rel:'stylesheet', type:'text/css', href:'http://attnfeed.com/static/menuMatic.css'}))
page.head.write(SCRIPT({src:"http://agglodex.uservoice.com/pages/general/widgets/tab.js?alignment=right&amp;color="+randomColor()}))
page.head.write('<meta name="verify-v1" content="OVXG83ENOaAO4o+ZF7VgY3s9LH1KX+EV6G8XDzRkIZw=" />')

// content globals must be available before dispatch() is called
var pageTitle = 'All Your Feeds'
var content = DIV({
    id:'content', 
    className:request.path.slice(1)
})
if(!error) var error = ''
if(!message) var message = ''

if(request.path.match(/^\/profile\/.*$/))
    get_profile()
else
    dispatch()

page.setTitle(pageTitle+" / Agglodex.com")
var container = DIV({id:'container'})
var my_nav = UL({id:'my_nav'})
if(!user && request.path != 'logout') {
    my_nav.push(
        LI(link('/register', 'Register')),
        LI(link('/login', 'Login'))
    )
} else if(request.path != 'login' && request.path != 'register') {
    my_nav.push(
        //LI(link('/home', 'Home')),
        LI(link('/profile/'+user.name, 'My Profile')),
        LI(link('/my_feeds', 'My Feeds')),
        LI(link('/my_entries', 'My Entries')),
        LI(link('/my_interests', 'My Interests')),
        LI(link('/my_similar_users', 'My Users')),
        LI(link('/logout', 'Logout'))
    )
}
container.push(DIV({id:'header'},
    H1(link('/', "Agglodex")),
    H2({className:'pageTitle'}, pageTitle),
    UL({id:'nav'},
        LI(A("Mine"), my_nav),
        LI(A("All"), UL({id:'all_nav'},
            LI(link('/users', 'Users')),
            LI(link('/feeds', 'Feeds')),
            LI(link('/entries', 'Entries')),
            LI(link('/terms', 'Terms'))
        ))
    )
))
if(message) container.push(DIV({className:'message'}, message))
if(error) container.push(DIV({className:'error'}, error))
container.push(content)
container.push(DL({id:'footer'},
    DT(A({
        rel:'license', 
        href:'http://creativecommons.org/licenses/by-nc-sa/3.0/us/'
    }, raw("&copy;"))), 
    DD("2008"),
    DT("by"),
    DD(link('http://appjet.com/user/jesse/profile', 'jesse')),
    DT("?->"),
    DD(link("http://comments."+appjet.appName+"."+appjet.mainDomain,'comments')),
    DT(),
    DD(link('/blog', "blog")),
    DT(link('/users', 'U')),
    DD(storage.users.size()),
    DT(link('/feeds', 'F')),
    DD(storage.feeds.size()),
    DT(link('/entries', 'E')),
    DD(storage.entries.size()),
    DT(link('/terms', 'T')),
    DD(storage.terms.size())
))
container.push(SCRIPT({src:'http://attnfeed.com/static/mootools-1.2-core.js'}))
container.push(SCRIPT({src:'http://attnfeed.com/static/mootools-1.2-more.js'}))
container.push(SCRIPT({src:'http://attnfeed.com/static/mooRainbow.js'}))
//container.push(SCRIPT({src:'http://attnfeed.com/static/agglodex.js'}))
//container.push(SCRIPT({src:'http://attnfeed.com/static/menuMatic_0.67.js'}))
//container.push(SCRIPT('var mm = new MenuMatic({menuWidthMode:true}); '))
container.push(raw("""
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-3571135-3");
pageTracker._trackPageview();
</script>
"""))
if(user) 
    container.push(raw('<script type="text/javascript">pageTracker._setVar("'+user.email+'");</script>'))

print(container)
//if(appjet.isPreview) Profile()

